﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WeddingEventManagement_API
{
    public class EventManager : IEventManager
    {

        #region Validation

        private void CheckIfClientExistsActive(int clientID)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                Client client = db.Clients.FirstOrDefault(i => i.ClientID == clientID);
                if(client == null)
                {
                    throw new InvalidClientException(String.Format("You have attempted to add an event to a non existing client (ClientID: {0}", clientID), 
                        clientID);
                }
                if(client.IsActive == false)
                {
                    throw new InvalidClientException(String.Format("You have attempted to add an event to an inactive client. Client name: '{0}'. ClientID: '{1}'",
                        client.FirstName + " " + client.LastName, clientID), 
                        clientID);
                }
            }
        }

        private void CheckIfEventExists(int clientID, string title, DateTime startDate, DateTime endDate, EventTypeEnum eventType)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                Event evt = db.Events.FirstOrDefault(i => i.ClientID == clientID
                    && i.Title == title
                    && i.StartDate == startDate
                    && i.EndDate == endDate
                    && i.EventTypeID == (int)eventType);

                if (evt != null)
                {
                    Client client = new ClientManager().GetClient(evt.ClientID);
                    throw new EventExistsException(String.Format("An event already exists of Type: '{3}' with the Title: '{0}' for Client: '{1}' on: '{2}'",
                        title, client.FirstName + " " + client.LastName, startDate.ToShortDateString(), eventType.ToString()), 
                        evt); 
                }
            }
        }

        #endregion

        #region Create, Update, Delete

        public int CreateEvent(int clientID, string title, DateTime startDate, DateTime endDate, EventTypeEnum eventType,
            PackageType packageType, Guid createdByUserID, string notes, bool flowers, decimal flowersBudget, bool decor, decimal decorBudget, bool coordination, int guests)
        {
            CheckIfEventExists(clientID, title, startDate, endDate, eventType);
            CheckIfClientExistsActive(clientID);

            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                DateTime createdDate = DateTime.UtcNow;
                Event csEvent = new Event
                {
                    ClientID = clientID,
                    CreatedDate = createdDate,
                    EndDate = endDate,
                    EventTypeID = (int)eventType,
                    PackageID = coordination ? (int)packageType : 1005,
                    StartDate = startDate,
                    Title = title,
                    CreatorUserID = createdByUserID,
                    Coordination = coordination,
                    Decor = decor,
                    Flowers = flowers,
                    FlowersBudget = flowersBudget,
                    DecorBudget = decorBudget,
                    Guests = guests,
                    Notes = notes
                };
                db.Events.Add(csEvent);
                db.SaveChanges();

                Event newEvent = db.Events.FirstOrDefault(i => i.ClientID == clientID
                    && i.CreatedDate == createdDate
                    && i.EndDate == endDate
                    && i.EventTypeID == (int)eventType
                    && i.PackageID == (int)packageType
                    && i.StartDate == startDate
                    && i.Title == title
                    && i.CreatorUserID == createdByUserID
                );

                return newEvent.EventID;
            }

        }

        public void DeleteEvent(int eventID)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                //remove rows from invoice
                Invoice invoice = db.Invoices.FirstOrDefault(i => i.EventID == eventID);
                if (invoice != null)
                {
                    //remove payments
                    IEnumerable<Payment> payments = db.Payments.Where(i => i.InvoiceID == invoice.InvoiceID);
                    foreach (Payment payment in payments)
                    {
                        db.Payments.Remove(payment);
                    }
                    db.SaveChanges();

                    IEnumerable<InvoiceItem> invoiceItems = db.InvoiceItems.Where(i => i.InvoiceID == invoice.InvoiceID);
                    bool invoiceItemsDeleted = false;
                    foreach (InvoiceItem invoiceItem in invoiceItems)
                    {
                        invoiceItemsDeleted = true;
                        db.InvoiceItems.Remove(invoiceItem);
                    }
                    if (invoiceItemsDeleted)
                    {
                        db.SaveChanges();
                    }

                    db.Invoices.Remove(invoice);
                    db.SaveChanges();
                }

                //Remove rows from intersection table
                var eventLocations = db.EventLocations.Where(i => i.EventID == eventID);
                foreach(EventLocation eventLocation in eventLocations)
                {
                    db.EventLocations.Remove(eventLocation);
                }
                db.SaveChanges();

                //Remove event
                Event csEvent = db.Events.FirstOrDefault(i => i.EventID == eventID);
                db.Events.Remove(csEvent);
                db.SaveChanges();
            }
        }

        public void UpdateEvent(Event csEvent, Guid updateUserID)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                Event origEvent = db.Events.FirstOrDefault(i => i.EventID == csEvent.EventID);
                origEvent.EndDate = csEvent.EndDate;
                origEvent.EventTypeID = csEvent.EventTypeID;
                origEvent.LastUpdated = csEvent.LastUpdated;
                origEvent.LastUpdatedUserID = csEvent.LastUpdatedUserID;
                origEvent.LastViewed = csEvent.LastViewed;
                origEvent.PackageID = csEvent.PackageID;
                origEvent.StartDate = csEvent.StartDate;
                origEvent.Title = csEvent.Title;
                origEvent.Notes = csEvent.Notes;
                origEvent.Coordination = csEvent.Coordination;
                origEvent.Decor = csEvent.Decor;
                origEvent.DecorBudget = csEvent.DecorBudget;
                origEvent.Flowers = csEvent.Flowers;
                origEvent.FlowersBudget = csEvent.FlowersBudget;
                origEvent.Guests = csEvent.Guests;

                foreach (var evtLocation in csEvent.EventLocations)
                {
                    if (evtLocation.Event == null)
                    {
                        db.EventLocations.Add(new EventLocation() 
                        { 
                            LocationType = evtLocation.LocationType, EventID = origEvent.EventID, LocationID = evtLocation.LocationID, Notes = evtLocation.Notes, Time = evtLocation.Time
                        });
                    }
                }

                //update dates
                origEvent.LastUpdatedUserID = updateUserID;
                origEvent.LastUpdated = DateTime.UtcNow;

                db.Entry(origEvent).State = System.Data.EntityState.Modified;
                db.SaveChanges();
            }
        }

        #endregion

        #region Read

        /// <summary>
        /// Gets a single event by EventID
        /// </summary>
        /// <param name="eventID"></param>
        /// <returns></returns>
        public Event GetEvent(int eventID)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                db.Configuration.LazyLoadingEnabled = false;

                return db.Events
                    .Include("Client")
                    .Include("Package")
                    .Include("EventType")
                    .Include("EventLocations")
                    .Include("EventLocations.Location")
                    .Include("Invoices")
                    .Include("Invoices.Payments")
                    .FirstOrDefault(i => i.EventID == eventID);
            }
        }

        /// <summary>
        /// Returns List of event objects
        /// </summary>
        /// <returns></returns>
        public List<Event> GetEvents()
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                //db.Configuration.LazyLoadingEnabled = false;

                return db.Events.Include("EventType").ToList();
            }
        }

        public List<Event> GetEvents(long start, long end)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                DateTime _start = UnixTimeStampToDateTime((double)start);
                DateTime _end = UnixTimeStampToDateTime((double)end);
                return db.Events.Include("EventType").Where(e => e.StartDate >= _start && e.StartDate <= _end).ToList();
            }
        }

        /// <summary>
        /// Get all events for a given client
        /// </summary>
        /// <param name="clientID"></param>
        /// <returns></returns>
        public List<Event> GetEventsForLocation(int locationId)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                db.Configuration.LazyLoadingEnabled = false;
                
                List<EventLocation> eventLocations = db.EventLocations.Where(i => i.LocationID == locationId).ToList();

                List<int> eventIds = new List<int>();
                foreach (EventLocation eventLocation in eventLocations)
                {
                    if (!eventIds.Contains(eventLocation.EventID))
                    {
                        eventIds.Add(eventLocation.EventID);
                    }
                }

                return db.Events.Include("Invoices").Include("Client").Include("EventType").Where(i => eventIds.Contains(i.EventID)).ToList();
            }
        }

        /// <summary>
        /// Get all events for a given client
        /// </summary>
        /// <param name="clientID"></param>
        /// <returns></returns>
        public List<Event> GetEvents(int clientID)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                db.Configuration.LazyLoadingEnabled = false;

                return db.Events.Where(i => i.ClientID == clientID).ToList();
            }
        }

        /// <summary>
        /// Get events filtered by event title
        /// </summary>
        /// <returns>List of Events</returns>
        public List<Event> GetEvents(string filter)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                //db.Configuration.LazyLoadingEnabled = false;

                return db.Events.Include("EventType").Where(i => i.Title.Contains(filter)).ToList();
            }
        }

        /// <summary>
        /// Returns recently viewed Events
        /// </summary>
        /// <param name="num">Number of events to return</param>
        public List<Event> GetMostRecentlyViewedEvents(int num)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                return db.Events.Include("EventType").Where(i => i.LastViewed != null).OrderByDescending(i => i.LastViewed.Value).Take(num).ToList();
            }
        }

        /// <summary>
        /// Returns recently added events
        /// </summary>
        /// <param name="num">Number of events to return</param>
        public List<Event> GetMostRecentlyAddedEvents(int num)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                return db.Events.Include("EventType").OrderByDescending(i => i.CreatedDate).Take(num).ToList();
            }
        }

        public List<Event> GetUpcomingEvents(int num)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                var today = DateTime.Now.Date;
                return db.Events.Include("EventType").Where(i => i.StartDate > today ).OrderBy(i => i.StartDate).Take(num).ToList();
            }
        }

        #endregion

        #region EventLocations

        public EventLocation GetEventLocation(int eventID, int locationID)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                return db.EventLocations.Include("Location").FirstOrDefault(i => i.EventID == eventID && i.LocationID == locationID);
            }
        }

        public void UpdateEventLocation(EventLocation eventLocation, Guid updateUserID)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                db.EventLocations.Attach(eventLocation);
                db.Entry(eventLocation).State = System.Data.EntityState.Modified;

                Event evt = db.Events.FirstOrDefault(i => i.EventID == eventLocation.EventID);
                evt.LastUpdated = DateTime.UtcNow;
                evt.LastUpdatedUserID = updateUserID;

                db.SaveChanges();
            }
        }

        #endregion

        public static DateTime UnixTimeStampToDateTime(double unixTimeStamp)
        {
            // Unix timestamp is seconds past epoch
            System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
            dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime();
            return dtDateTime;
        }

        #region Event Types

        /// <summary>
        /// Gets All EventTypes where IsActive is set to True
        /// </summary>
        /// <returns></returns>
        public List<EventType> GetAllEventTypes()
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                return db.EventTypes
                    .Where(t  => t.IsActive)
                    .Distinct()
                    .ToList();
            }
        }

        /// <summary>
        /// Creates a new EventType and adds it to the database
        /// </summary>
        /// <param name="name"></param>
        /// <param name="color"></param>
        public void CreateEventType(string name, string color)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                db.EventTypes.Add(new EventType() 
                { 
                    EventTypeName = name, 
                    CalendarColor = color, 
                    Events = new List<Event>(),
                    IsActive = true
                });
                db.SaveChanges();
            }
        }

        /// <summary>
        /// Disables a Event Type from appearing in dropdown lists
        /// </summary>
        /// <param name="id"></param>
        public void DeleteEventType(int id)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                db.EventTypes.Where(t => t.EventTypeID == id).SingleOrDefault()
                    .IsActive = false;
                db.SaveChanges();
            }
        }

        #endregion

        #region Packages

        /// <summary>
        /// Gets all packages where IsActive is set to True
        /// </summary>
        /// <returns></returns>
        public List<Package> GetAllPackages()
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                return db.Packages
                    .Where(p => p.IsActive)
                    .Distinct()
                    .ToList();
            }
        }

        /// <summary>
        /// Creates a new package and adds it to the database
        /// </summary>
        /// <param name="name"></param>
        /// <param name="price"></param>
        /// <param name="description"></param>
        /// <param name="color"></param>
        public void CreatePackage(string name, decimal price, string description, string color)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                db.Packages.Add( new Package() 
                { 
                    PackageType = name, 
                    BasePrice = price,
                    Color = color,
                    Description = description,
                    IsActive = true
                });
                db.SaveChanges();
            }
        }

        /// <summary>
        /// Disables a package from appearing in dropdown lists
        /// </summary>
        /// <param name="id"></param>
        public void DeletePackage(int id)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                db.Packages.Where(p => p.PackageID == id).SingleOrDefault()
                    .IsActive = false;
                db.SaveChanges();
            }
        }

        #endregion

    }
}
